package com.geeguo.ebuilder.client.base.database.aspect;

import javax.sql.DataSource;

import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.geeguo.ebuilder.client.base.database.model.DataSourceInfo;
import com.geeguo.ebuilder.client.base.database.proxy.DataSourceInfoProxy;
import com.geeguo.ebuilder.client.base.tenant.model.DataContext;
import com.geeguo.ebuilder.client.base.tenant.model.TenantInfo;
import com.geeguo.ebuilder.client.base.tenant.provider.TenantProvider;
import com.geeguo.ebuilder.client.base.tenant.utils.DataContextHolder;
import com.geeguo.ebuilder.core.common.exception.FrameworkException;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.druid.DruidDataSourceCreator;
import org.springframework.stereotype.Component;

@Aspect
@Order(0) // 比事务切面的优先级更高，越小优先级越高（事务切面默认是Ordered.LOWEST_PRECEDENCE）
@Component
public class DataSourceSwitchAspect {
	
	@Autowired
    private DataSource dataSource;
	@Autowired
	private DataSourceInfoProxy dataSourceInfoProxy;
	@Autowired
    private DruidDataSourceCreator druidDataSourceCreator;

	/**
     * 切入点
	 * 说明：
	 * 1、切入点为@TDS注解标注的方法，即需要切换租户数据源的方法。
	 * 2、不要将@TDS注解标注在不需要访问数据库的方法上，减少切换数据源的开销，这也是@TDS注解不支持在类上的原因，防止类中有不访问数据库的方法。
     */
	@Pointcut("@annotation(com.geeguo.ebuilder.client.base.database.annotation.TDS)")
    public void pointcut() {
    	
    }
    
    /**
     * 环绕切入
     * 
     * @param joinPoint 切面对象
     * @return 底层方法执行后的返回值
     * @throws Throwable 底层方法抛出的异常
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		DataContext dataContext = getDataContext(joinPoint);
		if (dataContext == null || StringUtils.isBlank(dataContext.getTenantCode())) {
			throw new FrameworkException("未发现租户编码，无法确定数据源。");
		}
		TenantInfo tenantInfo = TenantProvider.getByCode(dataContext.getTenantCode());
		if (tenantInfo == null) {
			throw new FrameworkException("未发现租户编码" + dataContext.getTenantCode() + "对应的租户信息。");
		}
		String dataSourceId = tenantInfo.getDataSourceId();
		if (StringUtils.isBlank(dataSourceId)) {
			throw new FrameworkException("租户编码" + dataContext.getTenantCode() + "对应的租户未设置数据源。");
		}
		DynamicRoutingDataSource dynamicRoutingDataSource = (DynamicRoutingDataSource) dataSource;
		if (!dynamicRoutingDataSource.getDataSources().containsKey(dataSourceId)) {
			DataSource dataSource = buildDataSource(dataSourceId);
			if (dataSource != null) {
				dynamicRoutingDataSource.addDataSource(dataSourceId, dataSource);
			} else {
				throw new FrameworkException("租户编码" + dataContext.getTenantCode() + "对应的租户无法初始化数据源。");
			}
		}
		// 设置数据源标识
		DynamicDataSourceContextHolder.push(dataSourceId);
		try {
			// 执行原有逻辑
			return joinPoint.proceed();
		} finally {
			DynamicDataSourceContextHolder.poll();
		}
    }

	/**
	 * 获取数据访问上下文
	 * 说明：
	 * 1、获取数据访问上下文对象，优先从方法参数中获取，如果没有则从线程变量中获取。
	 * 2、建议在方法参数中显式传入数据访问上下文对象，尽量不把数据访问上下文对象放入到线程变量中，减少内存溢出风险。
	 */
	private DataContext getDataContext(ProceedingJoinPoint joinPoint) {
		DataContext dataContext = null;
		Object[] args = joinPoint.getArgs();
		if (args != null) {
			for (Object arg : args) {
				if (arg instanceof DataContext) {
					dataContext = (DataContext) arg;
					break;
				}
			}
		}
		if (dataContext == null) {
			dataContext = DataContextHolder.getDataContext();
		}
		return dataContext;
	}

	private DataSource buildDataSource(String dataSourceId) {
		DataSourceInfo dataSourceInfo = dataSourceInfoProxy.get(dataSourceId);
        String url = "jdbc:mysql://" +
                dataSourceInfo.getHost() +
                ":" +
                dataSourceInfo.getPort() +
                "/" +
                dataSourceInfo.getDbName() +
                "?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";

		DataSourceProperty dataSourceProperty = new DataSourceProperty();
		dataSourceProperty.setType(DruidDataSource.class);
		dataSourceProperty.setDriverClassName("com.mysql.cj.jdbc.Driver");
		dataSourceProperty.setUrl(url);
		dataSourceProperty.setUsername(dataSourceInfo.getUsername());
		dataSourceProperty.setPassword(dataSourceInfo.getPassword());
		dataSourceProperty.getDruid().setInitialSize(dataSourceInfo.getInitialSize());
		dataSourceProperty.getDruid().setMinIdle(dataSourceInfo.getInitialSize());
		dataSourceProperty.getDruid().setMaxActive(dataSourceInfo.getMaxActive());
		// 获取连接等待超时的时间，单位毫秒
		dataSourceProperty.getDruid().setMaxWait(10000);
		// 间隔多久进行一次检测，检测需要关闭的空闲连接，单位毫秒
		dataSourceProperty.getDruid().setTimeBetweenEvictionRunsMillis(60000L);
		// 一个连接在池中最小生存时间
		dataSourceProperty.getDruid().setMinEvictableIdleTimeMillis(300000L);
		dataSourceProperty.getDruid().setTestWhileIdle(true);
		dataSourceProperty.getDruid().setTestOnBorrow(false);
		dataSourceProperty.getDruid().setTestOnReturn(false);
		return druidDataSourceCreator.createDataSource(dataSourceProperty);
	}
}